﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace Xceed.Silverlight.Data
{
  internal class DataPath : IEnumerable<DataItem>
  {
    public static readonly DataPath Empty = new DataPath( new DataItem[ 0 ] );

    // Create an instance with a list of 1 item.
    public DataPath( DataItem obj )
    {
      if( obj == null )
        throw new ArgumentNullException( "obj" );

      m_path = new DataItem[] { obj };
    }

    public DataPath( DataItem[] objectPath )
      : this( objectPath, objectPath.Length, false )
    {
    }

    public DataPath( DataItem[] objectPath, int length )
      : this( objectPath, length, false )
    {
    }

    internal DataPath( DataItem[] objectPath, bool useClientArray )
      : this( objectPath, objectPath.Length, useClientArray )
    {
    }

    internal DataPath( DataItem[] objectPath, int length, bool useClientArray )
    {
      if( objectPath == null )
        throw new ArgumentNullException( "objectPath" );

      if( length < 0 )
        throw new ArgumentOutOfRangeException( "length", "length must be greater than or equal to zero." );

      if( objectPath.Length < length )
        throw new ArgumentException( "The length of the specified DataItem array must be greater than or equal to the specified length.", "objectPath" );

      for( int i = 0; i < length; i++ )
      {
        if( objectPath[ i ] == null )
          throw new ArgumentException( "The specified DataItem array cannot contain null items.", "objectPath" );
      }

      if( !useClientArray )
      {
        DataItem[] newPath = new DataItem[ length ];
        Array.Copy( objectPath, newPath, length );
        m_path = newPath;
      }
      else
      {
        if(length != objectPath.Length)
          throw new InvalidOperationException( "The objectPath length is inconsistent with the length parameter." );

        m_path = objectPath;
      }
    }

    public DataPath( DataItem[] parentPath, DataItem child )
    {
      if( parentPath == null )
        throw new ArgumentNullException( "parentPath" );

      if( child == null )
        throw new ArgumentNullException( "child" );

      m_path = new DataItem[ parentPath.Length + 1 ];
      Array.Copy( parentPath, m_path, parentPath.Length );
      m_path[ m_path.Length - 1 ] = child;
    }


    public DataPath( DataItem rootItem, DataItem[] children )
    {
      if( rootItem == null )
        throw new ArgumentNullException( "rootItem" );

      if( children == null )
        throw new ArgumentNullException( "children" );

      m_path = new DataItem[ children.Length + 1 ];
      m_path[ 0 ] = rootItem;
      Array.Copy( children, 0, m_path, 1, children.Length );
    }


    public DataPath ParentPath
    {
      get
      {
        if( m_path.Length == 1 )
          return null;
        DataItem[] ret = new DataItem[ m_path.Length - 1 ];
        Array.Copy( m_path, ret, ret.Length );
        return new DataPath( ret, true );
      }
    }

    public DataItem LastChild
    {
      get
      {
        return m_path[ m_path.Length - 1 ];
      }
    }

    public int Depth
    {
      get
      {
        return m_path.Length;
      }
    }

    internal DataItem[] Path
    {
      get
      {
        return m_path;
      }
    }

    public DataItem this[ int index ]
    {
      get
      {
        if( index >= this.Depth )
          throw new ArgumentOutOfRangeException( "index", "index must be less than the path depth.");

        return m_path[ index ];
      }
      internal set
      {
        if( index >= this.Depth )
          throw new ArgumentOutOfRangeException( "index", "index must be less than the path depth." );

        if( value == null )
          throw new ArgumentNullException( "Index" );

        m_path[ index ] = value;

      }
    }

    public DataPath CreateChildPath( DataItem child )
    {
      return new DataPath( this.m_path, child );
    }

    public DataPath CreateChildPath( DataPath childPath, int startIndex, int length )
    {
      int childDepth = childPath.Depth;

      if( startIndex >= childDepth )
        throw new ArgumentOutOfRangeException( "startIndex", "startIndex must be less than the childPath's depth." );

      if( startIndex + length > childDepth )
        throw new ArgumentOutOfRangeException( "length", "length must be less than or equal to the childPath's scope." );

      DataItem[] newArray = new DataItem[ this.Depth + length ];

      Array.Copy( m_path, 0, newArray, 0, m_path.Length );
      Array.Copy( childPath.m_path, startIndex, newArray, m_path.Length, length );

      return new DataPath( newArray, true );
    }

    public DataPath CreateAncestorPath( int depth )
    {
      if( depth > this.Depth )
        throw new ArgumentOutOfRangeException( "depth", "depth must be less than or equal to the actual path depth." );

      return this.CreateAncestorOrSamePath( depth );
    }

    public DataPath CreateAncestorOrSamePath( int depth )
    {

      DataItem[] newArray = new DataItem[ depth ];

      Array.Copy( this.m_path, 0, newArray, 0, depth );

      DataPath newPath = new DataPath( newArray, true );

      return newPath;
    }

    // of the current path.
    public bool IsAncestorOf( DataPath childPath )
    {
      if( this.m_path.Length < childPath.m_path.Length )
      {
        return DataPath.AreSameItems( m_path, childPath.m_path, m_path.Length );
      }
      return false;
    }

    internal bool IsAncestorOf( DataItem[] arrayPath, int validDepth )
    {
      if( this.m_path.Length < validDepth ) // childPath.m_path.Length )
      {
        return DataPath.AreSameItems( m_path, arrayPath, m_path.Length );
      }
      return false;
    }

    public DataItem[] ToArray()
    {
      DataItem[] newArray = new DataItem[ this.Depth ];
      m_path.CopyTo( newArray, 0 );
      return newArray;
    }   

    public bool IsDescendantOf( DataPath parentPath )
    {
      if( parentPath == null )
        throw new ArgumentNullException( "parentPath" );

      return parentPath.IsAncestorOf( this );
    }

    public override int GetHashCode()
    {
      int hashcode = 0;

      int length = m_path.Length;
      for( int i = 0; i < length; i++ )
      {
        hashcode = hashcode ^ m_path[ i ].GetHashCode();
      }

      return hashcode;
    }

    public override bool Equals( object obj )
    {
      DataPath nodePath = obj as DataPath;
      if( ( nodePath == null ) || this.m_path.Length != nodePath.m_path.Length )
        return false;

      return DataPath.AreSameItems( m_path, nodePath.m_path, m_path.Length );
    }

    static internal bool AreSameItems( DataItem[] array1, DataItem[] array2, int len )
    {
      for( int i = 0; i < len; i++ )
      {
        if( !object.Equals( array1[ i ], array2[ i ] ) )
        {
          return false;
        }
      }
      return true;
    }

#if DEBUG
    public override string ToString()
    {
      if( m_path.Length == 0 )
        return "(Empty Path)";

      StringBuilder sb = new StringBuilder();
      bool first = true;
      foreach( DataItem dgi in m_path )
      {
        if( !first )
        {
          sb.Append( @"\" );
        }
        first = false;
        sb.Append( dgi.ToString() );
      }
      return sb.ToString();
    }
#endif  //DEBUG


    #region IEnumerable<DataItem> Members

    // with the topmost parent.
    public IEnumerator<DataItem> GetEnumerator()
    {
      foreach( DataItem dgi in m_path )
      {
        yield return dgi;
      }
    }

    #endregion

    #region IEnumerable Members

    IEnumerator IEnumerable.GetEnumerator()
    {
      return this.GetEnumerator();
    }

    #endregion

    private DataItem[] m_path;
  }
}
